import java.io.IOException;
import java.util.ArrayList;
import java.util.NoSuchElementException;

/**
   Represents a ReadyQueue
   @author Morton & Uemura
 */
public class ReadyQueue
{
	/** 
	   Round Robin Queue with shortest Time Quantum
	 */
	private RoundRobinQueue RRQ1;
	
	/**
	   Round Robin Queue with 2nd shortest Time Quantum
	 */
	private RoundRobinQueue RRQ2;
	
	/**
	   Round Robin Queue with 3rd shortest Time Quantum
	 */
	private RoundRobinQueue RRQ3;
	
	/**
	   Round Robin Queue with longest Time Quantum
	 */
	private RoundRobinQueue RRQ4;
	
	/**
	   Constructs a default ReadyQueue with 4 Round
	    Robin Queues set to 1, 2, 3, 4 with Priority 
	    Range [0-3], [4-7], [8-11] & [12-15] Respectively
	    (0 being highest & 15 lowest).
	    Also, with Time Quantum 8, 16, 30 and 50 msec 
	    respectively.
	 */
	public ReadyQueue()
	{
		RRQ1 = new RoundRobinQueue(1, new int[] {0, 1, 2, 3}, 8);
		RRQ2 = new RoundRobinQueue(2, new int[] {4, 5, 6, 7}, 16);
		RRQ3 = new RoundRobinQueue(3, new int[] {8, 9, 10, 11}, 30);
		RRQ4 = new RoundRobinQueue(4, new int[] {12, 13, 14, 15}, 50);
	}
	
	/**
	   Adds process to appropriate RoundRobinQueue
	    PRECONDITION: Does not check for invalid Process priority
	   @param toAdd Process to add
	   
	 */
	public void add(Process toAdd)
	{
		String qID = currentQueueID(toAdd.getCurrentPriorityID());
		toAdd.setCurrentTimeSlice(getTimeQuantum(qID));
		if (qID.equals("Q1"))
			RRQ1.add(toAdd);
		else if (qID.equals("Q2"))
			RRQ2.add(toAdd);
		else if (qID.equals("Q3"))
			RRQ3.add(toAdd);
		else 
			RRQ4.add(toAdd);
	}
	
	/**
	   Gets a String Representation of the Queue ID
	   @param priority Priority Level
	   @return String Representation of the Queue ID
	 */
	public String currentQueueID(int priority)
	{
		if (priority >= 0 && priority <= 3)
			return "Q1";
		else if (priority >= 4 && priority <= 7)
			return "Q2";
		else if (priority >= 8 && priority <= 11)
			return "Q3";
		else 
			return "Q4";
	}
	
	/**
	   Removes and returns a Process if contained
	    in any one of the four Round Robin Queues
	   @param toRid Process to remove
	   @throws NoSuchElementException if Process not found
	   @return removed Process
	 */
	public Process remove(Process toRid)
	{
		if (toRid.equals(RRQ1.peek()))
			return RRQ1.remove();
		else if (toRid.equals(RRQ2.peek()))
			return RRQ2.remove();
		else if (toRid.equals(RRQ3.peek()))
			return RRQ3.remove();
		else if (toRid.equals(RRQ4.peek()))
			return RRQ4.remove();
		else
			throw new NoSuchElementException();
	}
	
	/**
	   Updates Wait time for every processes in ReadyQueue
	   @param tick unit increment time
	 */
	public void updateProcessWaitTimes(double tick)
	{
		// updated RRQ1
		for (int i = 0; i < RRQ1.size(); i++)
			RRQ1.get(i).incrementWaitTime(tick);
		// updated RRQ2
		for (int i = 0; i < RRQ2.size(); i++)
			RRQ2.get(i).incrementWaitTime(tick);
		// updated RRQ3
		for (int i = 0; i < RRQ3.size(); i++)
			RRQ3.get(i).incrementWaitTime(tick);
		// updated RRQ4
		for (int i = 0; i < RRQ4.size(); i++)
			RRQ4.get(i).incrementWaitTime(tick);
	}
	
	/**
	   Method to prevent Aging by increasing every
	    process in Round Robin Queues 2 to 4 by 1.
	    Example: Process with Priority 10 becomes 9
	    All Processes in Queues are inserted into 
	    the correct new Queues and settings adjusted
	    if the increase incurs a Queue Shift
	   @param trace for trace true, else false
	 */
	public void preventAging(boolean trace)
	{
		ArrayList <Process> all = new ArrayList <Process> ();
		
		while (!RRQ2.isEmpty())
			all.add(RRQ2.remove());
		while (!RRQ3.isEmpty())
			all.add(RRQ3.remove());
		while (!RRQ4.isEmpty())
			all.add(RRQ4.remove());
		
		for (Process p : all)
		{
			int id = p.getCurrentPriorityID() - 1;
			p.setCurrentPriorityID(id);
			try {
				p.setCurrentQueueNumber(id);
			} catch (IOException e) {
				e.printStackTrace(); // Never Reached
			}
			String qID = currentQueueID(p.getCurrentPriorityID());
			p.setCurrentTimeSlice(getTimeQuantum(qID));
			if (trace && (id == 3 || id == 7 || id == 11))
			{
				String t = "*****\t\tAging Occured to Process " +
					p.getID() + " Moved to Queue" + 
					p.getCurrentQueueNumber();
				System.out.println(t);
			}
			if (id <= 3)
				RRQ1.add(p);
			else if (id >= 4 && id <= 7)
				RRQ2.add(p);
			else if (id >= 8 && id <= 11)
				RRQ3.add(p);
			else
				RRQ4.add(p);
		}
	}
	
	/**
	   Returns the next Process to CPU
	    based on the Ready Queue Scheduling
	    Mechanism. 
	   @return next Process for CPU
	 */
	public Process nextProcessToCPU()
	{
		if (!RRQ1.isEmpty())
			return RRQ1.getFirst();
		else if (!RRQ2.isEmpty())
			return RRQ2.getFirst();
		else if (!RRQ3.isEmpty())
			return RRQ3.getFirst();
		else if (!RRQ4.isEmpty())
			return RRQ4.getFirst();
		else
			return null; // All queues are empty.
	}
	
	/**
	   Returns a String representation of the 
	    contents of the four Round Robin Queues in a Ready Queue
	 */
	public String toString()
	{
		String status = "Queues contents (remaining CPU time)\n";
		// Contents of RRQ1
		status += RRQ1.toString();
		status += "\n";
		
		// Contents of RRQ2
		status += RRQ2.toString();
		status += "\n";
	
		// Contents of RRQ3
		status += RRQ3.toString();
		status += "\n";
		
		// Contents of RRQ4
		status += RRQ4.toString();
		status += "\n";
		
		return status;
	}
	
	/**
	   Gets the time slice/quantum for the specified
	    Round Robin Queue
	   	PRECONDITION: Returns -1 for invalid Queue Number
	   @param rrqi Round Robin Queue Number
	   @return time quantum of specified queue 
	 */
	public int getTimeQuantum(String rrqi)
	{
		if (rrqi.equals("Q1")) return RRQ1.getQuantum();
		else if (rrqi.equals("Q2")) return RRQ2.getQuantum();
		else if (rrqi.equals("Q3")) return RRQ3.getQuantum();
		else if (rrqi.equals("Q4")) return RRQ4.getQuantum();
		else return -1;
	}
	
	/**
	   Gets the time slice/quantum for the specified
	    Round Robin Queue
	   	PRECONDITION: Returns -1 for invalid Queue Number
	   @param rrqi Round Robin Queue Number
	   @return time quantum of specified queue 
	 */
	public int getTimeQuantum(int rrqi)
	{
		if (rrqi == 1) return RRQ1.getQuantum();
		else if (rrqi == 2) return RRQ2.getQuantum();
		else if (rrqi == 3) return RRQ3.getQuantum();
		else if (rrqi == 4) return RRQ4.getQuantum();
		else return -1;
	}
}
